/*
 * Group:PrePool K9-357 
 * 8/2010
 */
package core;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;

import util.Constants;

/**
 *
 */
public class IdxFile {
	private String strFileName; // path to the ".idx" file.
	private long longWordCount; // number of entries.
	private long longIdxFileSize;

	public boolean boolIsLoaded = false; // decide if the properties are loaded
	public List<WordEntry> entryList; // store the list of entries

	public String getStrFileName() {
		return strFileName;
	}

	public void setStrFileName(String strFileName) {
		this.strFileName = strFileName;
	}

	public long getLongWordCount() {
		return longWordCount;
	}

	public long getLongIdxFileSize() {
		return longIdxFileSize;
	}

	public void setLongIdxFileSize(long longIdxFileSize) {
		this.longIdxFileSize = longIdxFileSize;
	}

	/**
	 * constructor
	 * 
	 * @param fileName
	 *            file's name
	 * @param wordCount
	 *            dictionary's wordCount
	 * @param fileSize
	 *            file's size
	 */
	public IdxFile(String fileName, long wordCount, long fileSize) {
		this.longWordCount = wordCount;
		this.longIdxFileSize = fileSize;
		this.strFileName = fileName;
		load();
	}

	/**
	 * load properties
	 */
	public void load() {
		if (boolIsLoaded || ((new java.io.File(strFileName)).exists()) == false) {
			return;
		}
		try {
			DataInputStream dt = new DataInputStream(new BufferedInputStream(
					new FileInputStream(strFileName)));
			byte[] bt = new byte[(int) longIdxFileSize];
			dt.read(bt);
			dt.close();
			entryList = new ArrayList<WordEntry>();
			int startPos; // start position of entry
			int endPos = Constants.FIRST_POS_INDEX_FILE; // end position of
			// entry
			WordEntry tempEntry = null;
			/*
			 * firstBytes = new byte[Constants.FIRST_POS_INDEX_FILE]; for(int
			 * i=0;i<Constants.FIRST_POS_INDEX_FILE;i++) firstBytes[i] = bt[i];
			 */

			for (long i = 0; i < longWordCount; i++) {
				tempEntry = new WordEntry();
				// read the word
				startPos = endPos;
				while (bt[endPos] != '\0') {
					endPos++;
				}
				tempEntry.strWord = new String(bt, startPos, endPos - startPos,
						"UTF8");
				tempEntry.strLwrWord = tempEntry.strWord.toLowerCase();
				// read the offset of the meaning (in .dict file)
				++endPos;
				tempEntry.longOffset = readAnInt32(bt, endPos);
				// read the size of the meaning (in .dict file)
				endPos += 4;
				tempEntry.longSize = readAnInt32(bt, endPos);
				endPos += 4;
				entryList.add(tempEntry);
			}
			boolIsLoaded = true;
		} catch (Exception ex) {
		}
	}

	/**
	 * reload .idx file
	 */
	public void reload() {
		boolIsLoaded = false;
		load();
	}

	/**
	 * convert 4 char array to an integer
	 * 
	 * @param str
	 * @param beginPos
	 * @return
	 */
	private long readAnInt32(byte[] str, int beginPos) {
		int firstByte = (0x000000FF & ((int) str[beginPos]));
		int secondByte = (0x000000FF & ((int) str[beginPos + 1]));
		int thirdByte = (0x000000FF & ((int) str[beginPos + 2]));
		int fourthByte = (0x000000FF & ((int) str[beginPos + 3]));
		return ((long) (firstByte << 24 | secondByte << 16 | thirdByte << 8 | fourthByte)) & 0xFFFFFFFFL;
	}

	/**
	 * @param val
	 *            a integer
	 * @return a equivalent byte[]
	 */
	private byte[] convertAnInt32(int val) {
		byte[] str = new byte[4];
		str[0] = (byte) ((val & 0xFF000000) >> 24);
		str[1] = (byte) ((val & 0x00FF0000) >> 16);
		str[2] = (byte) ((val & 0x0000FF00) >> 8);
		str[3] = (byte) ((val & 0x000000FF));
		return str;
	}

	/**
	 * return the index of a word in entry list
	 * 
	 * @param word
	 * @return word's index
	 */
	public long findIndexForWord(String word) {
		if (!boolIsLoaded) {
			return longWordCount;
		}
		long first = 0;
		long last = (int) longWordCount - 1;
		long mid;
		String lwrWord = word.toLowerCase();

		// use binary search
		do {
			mid = (first + last) / 2;
			int cmp = lwrWord
					.compareTo(((WordEntry) entryList.get((int) mid)).strLwrWord);

			if (cmp == 0) {
				return mid; // return index if found
			}

			if (cmp > 0) {
				first = mid + 1;
			} else {
				last = mid - 1;
			}
		} while (first <= last);

		// if not found
		if (first < longWordCount) {
			while (first < longWordCount) {
				if (((WordEntry) entryList.get((int) first)).strLwrWord
						.compareTo(lwrWord) > 0) {
					break;
				} else {
					first++;
				}
			}
		}
		return first;
	}

	/**
	 * @param fileName
	 * @return
	 */
	public boolean write(String fileName) {
		try {
			DataOutputStream dt = new DataOutputStream(new FileOutputStream(
					fileName));
			// dt.write(firstBytes,0,Constants.FIRST_POS_INDEX_FILE);
			WordEntry tempEntry = null;

			for (int i = 0; i < (int) longWordCount; i++) {
				tempEntry = entryList.get(i);
				dt.write(tempEntry.strWord.getBytes("UTF8"));
				dt.write('\0');
				dt.write(convertAnInt32((int) tempEntry.longOffset));
				dt.write(convertAnInt32((int) tempEntry.longSize));
			}
			dt.flush();
			dt.close();
			return true;
		} catch (Exception ex) {
		}
		return false;
	}

	/**
	 * @return
	 */
	public boolean write() {
		return write(strFileName);
	}

	/**
	 * @param word
	 *            word's name
	 * @param offset
	 *            word's offset
	 * @param size
	 *            meaning's size
	 * @param addPos
	 *            word's position
	 * @return true if successful,false if fail
	 */
	public boolean addEntry(String word, long offset, long size, int addPos) {
		WordEntry etr = new WordEntry();
		etr.strWord = word;
		etr.strLwrWord = word.toLowerCase();
		etr.longOffset = offset;
		etr.longSize = size;
		
		if (addPos == -1) {
			addPos = (int) findIndexForWord(etr.strLwrWord);
		}
		
		if (addPos == longWordCount) {
			entryList.add(etr);
			this.longWordCount++;
			longIdxFileSize += (9 + word.length());
			return true;
		} else if (etr.strLwrWord.compareTo(((WordEntry) this.entryList
				.get(addPos)).strLwrWord) != 0) {
			entryList.add(addPos, etr);
			this.longWordCount++;
			longIdxFileSize += (9 + word.length());
			return true;
		}
		return false;
	}

	/**
	 * @param word
	 * @return
	 */
	public boolean removeEntry(String word) {
		String strLwrWord = word.toLowerCase();
		int pos = (int) findIndexForWord(strLwrWord);
		
		if (pos == longWordCount) {
			return false;
		} else if (strLwrWord
				.compareTo(((WordEntry) entryList.get(pos)).strLwrWord) != 0) {
			return false;
		} else {
			this.longWordCount--;
			longIdxFileSize -= (9 + word.length());
			entryList.remove(pos);
			return true;
		}
	}
}
